// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_INTERPRETER_CONTROL_FLOW_BUILDERS_H_
#define V8_INTERPRETER_CONTROL_FLOW_BUILDERS_H_

#include "src/interpreter/bytecode-array-builder.h"

#include "src/ast/ast-source-ranges.h"
#include "src/interpreter/block-coverage-builder.h"
#include "src/interpreter/bytecode-label.h"
#include "src/zone/zone-containers.h"

namespace v8 {
namespace internal {
    namespace interpreter {

        class V8_EXPORT_PRIVATE ControlFlowBuilder {
        public:
            explicit ControlFlowBuilder(BytecodeArrayBuilder* builder)
                : builder_(builder)
            {
            }
            virtual ~ControlFlowBuilder() = default;

        protected:
            BytecodeArrayBuilder* builder() const { return builder_; }

        private:
            BytecodeArrayBuilder* builder_;

            DISALLOW_COPY_AND_ASSIGN(ControlFlowBuilder);
        };

        class V8_EXPORT_PRIVATE BreakableControlFlowBuilder
            : public ControlFlowBuilder {
        public:
            BreakableControlFlowBuilder(BytecodeArrayBuilder* builder,
                BlockCoverageBuilder* block_coverage_builder,
                AstNode* node)
                : ControlFlowBuilder(builder)
                , break_labels_(builder->zone())
                , node_(node)
                , block_coverage_builder_(block_coverage_builder)
            {
            }
            ~BreakableControlFlowBuilder() override;

            // This method is called when visiting break statements in the AST.
            // Inserts a jump to an unbound label that is patched when the corresponding
            // BindBreakTarget is called.
            void Break() { EmitJump(&break_labels_); }
            void BreakIfTrue(BytecodeArrayBuilder::ToBooleanMode mode)
            {
                EmitJumpIfTrue(mode, &break_labels_);
            }
            void BreakIfFalse(BytecodeArrayBuilder::ToBooleanMode mode)
            {
                EmitJumpIfFalse(mode, &break_labels_);
            }
            void BreakIfUndefined() { EmitJumpIfUndefined(&break_labels_); }
            void BreakIfNull() { EmitJumpIfNull(&break_labels_); }

            BytecodeLabels* break_labels() { return &break_labels_; }

        protected:
            void EmitJump(BytecodeLabels* labels);
            void EmitJumpIfTrue(BytecodeArrayBuilder::ToBooleanMode mode,
                BytecodeLabels* labels);
            void EmitJumpIfFalse(BytecodeArrayBuilder::ToBooleanMode mode,
                BytecodeLabels* labels);
            void EmitJumpIfUndefined(BytecodeLabels* labels);
            void EmitJumpIfNull(BytecodeLabels* labels);

            // Called from the destructor to update sites that emit jumps for break.
            void BindBreakTarget();

            // Unbound labels that identify jumps for break statements in the code.
            BytecodeLabels break_labels_;

            // A continuation counter (for block coverage) is needed e.g. when
            // encountering a break statement.
            AstNode* node_;
            BlockCoverageBuilder* block_coverage_builder_;
        };

        // Class to track control flow for block statements (which can break in JS).
        class V8_EXPORT_PRIVATE BlockBuilder final
            : public BreakableControlFlowBuilder {
        public:
            BlockBuilder(BytecodeArrayBuilder* builder,
                BlockCoverageBuilder* block_coverage_builder,
                BreakableStatement* statement)
                : BreakableControlFlowBuilder(builder, block_coverage_builder,
                    statement)
            {
            }
        };

        // A class to help with co-ordinating break and continue statements with
        // their loop.
        class V8_EXPORT_PRIVATE LoopBuilder final : public BreakableControlFlowBuilder {
        public:
            LoopBuilder(BytecodeArrayBuilder* builder,
                BlockCoverageBuilder* block_coverage_builder, AstNode* node)
                : BreakableControlFlowBuilder(builder, block_coverage_builder, node)
                , continue_labels_(builder->zone())
            {
                if (block_coverage_builder_ != nullptr) {
                    block_coverage_body_slot_ = block_coverage_builder_->AllocateBlockCoverageSlot(
                        node, SourceRangeKind::kBody);
                }
            }
            ~LoopBuilder() override;

            void LoopHeader();
            void LoopBody();
            void JumpToHeader(int loop_depth);
            void BindContinueTarget();

            // This method is called when visiting continue statements in the AST.
            // Inserts a jump to an unbound label that is patched when BindContinueTarget
            // is called.
            void Continue() { EmitJump(&continue_labels_); }
            void ContinueIfUndefined() { EmitJumpIfUndefined(&continue_labels_); }
            void ContinueIfNull() { EmitJumpIfNull(&continue_labels_); }

        private:
            BytecodeLoopHeader loop_header_;

            // Unbound labels that identify jumps for continue statements in the code and
            // jumps from checking the loop condition to the header for do-while loops.
            BytecodeLabels continue_labels_;

            int block_coverage_body_slot_;
        };

        // A class to help with co-ordinating break statements with their switch.
        class V8_EXPORT_PRIVATE SwitchBuilder final
            : public BreakableControlFlowBuilder {
        public:
            SwitchBuilder(BytecodeArrayBuilder* builder,
                BlockCoverageBuilder* block_coverage_builder,
                SwitchStatement* statement, int number_of_cases)
                : BreakableControlFlowBuilder(builder, block_coverage_builder, statement)
                , case_sites_(builder->zone())
            {
                case_sites_.resize(number_of_cases);
            }
            ~SwitchBuilder() override; // NOLINT (modernize-use-equals-default)

            // This method should be called by the SwitchBuilder owner when the case
            // statement with |index| is emitted to update the case jump site.
            void SetCaseTarget(int index, CaseClause* clause);

            // This method is called when visiting case comparison operation for |index|.
            // Inserts a JumpIfTrue with ToBooleanMode |mode| to a unbound label that is
            // patched when the corresponding SetCaseTarget is called.
            void Case(BytecodeArrayBuilder::ToBooleanMode mode, int index)
            {
                builder()->JumpIfTrue(mode, &case_sites_.at(index));
            }

            // This method is called when all cases comparisons have been emitted if there
            // is a default case statement. Inserts a Jump to a unbound label that is
            // patched when the corresponding SetCaseTarget is called.
            void DefaultAt(int index) { builder()->Jump(&case_sites_.at(index)); }

        private:
            // Unbound labels that identify jumps for case statements in the code.
            ZoneVector<BytecodeLabel> case_sites_;
        };

        // A class to help with co-ordinating control flow in try-catch statements.
        class V8_EXPORT_PRIVATE TryCatchBuilder final : public ControlFlowBuilder {
        public:
            TryCatchBuilder(BytecodeArrayBuilder* builder,
                BlockCoverageBuilder* block_coverage_builder,
                TryCatchStatement* statement,
                HandlerTable::CatchPrediction catch_prediction)
                : ControlFlowBuilder(builder)
                , handler_id_(builder->NewHandlerEntry())
                , catch_prediction_(catch_prediction)
                , block_coverage_builder_(block_coverage_builder)
                , statement_(statement)
            {
            }

            ~TryCatchBuilder() override;

            void BeginTry(Register context);
            void EndTry();
            void EndCatch();

        private:
            int handler_id_;
            HandlerTable::CatchPrediction catch_prediction_;
            BytecodeLabel exit_;

            BlockCoverageBuilder* block_coverage_builder_;
            TryCatchStatement* statement_;
        };

        // A class to help with co-ordinating control flow in try-finally statements.
        class V8_EXPORT_PRIVATE TryFinallyBuilder final : public ControlFlowBuilder {
        public:
            TryFinallyBuilder(BytecodeArrayBuilder* builder,
                BlockCoverageBuilder* block_coverage_builder,
                TryFinallyStatement* statement,
                HandlerTable::CatchPrediction catch_prediction)
                : ControlFlowBuilder(builder)
                , handler_id_(builder->NewHandlerEntry())
                , catch_prediction_(catch_prediction)
                , finalization_sites_(builder->zone())
                , block_coverage_builder_(block_coverage_builder)
                , statement_(statement)
            {
            }

            ~TryFinallyBuilder() override;

            void BeginTry(Register context);
            void LeaveTry();
            void EndTry();
            void BeginHandler();
            void BeginFinally();
            void EndFinally();

        private:
            int handler_id_;
            HandlerTable::CatchPrediction catch_prediction_;
            BytecodeLabel handler_;

            // Unbound labels that identify jumps to the finally block in the code.
            BytecodeLabels finalization_sites_;

            BlockCoverageBuilder* block_coverage_builder_;
            TryFinallyStatement* statement_;
        };

        class V8_EXPORT_PRIVATE ConditionalControlFlowBuilder final
            : public ControlFlowBuilder {
        public:
            ConditionalControlFlowBuilder(BytecodeArrayBuilder* builder,
                BlockCoverageBuilder* block_coverage_builder,
                AstNode* node)
                : ControlFlowBuilder(builder)
                , end_labels_(builder->zone())
                , then_labels_(builder->zone())
                , else_labels_(builder->zone())
                , node_(node)
                , block_coverage_builder_(block_coverage_builder)
            {
                DCHECK(node->IsIfStatement() || node->IsConditional());
                if (block_coverage_builder != nullptr) {
                    block_coverage_then_slot_ = block_coverage_builder->AllocateBlockCoverageSlot(
                        node, SourceRangeKind::kThen);
                    block_coverage_else_slot_ = block_coverage_builder->AllocateBlockCoverageSlot(
                        node, SourceRangeKind::kElse);
                }
            }
            ~ConditionalControlFlowBuilder() override;

            BytecodeLabels* then_labels() { return &then_labels_; }
            BytecodeLabels* else_labels() { return &else_labels_; }

            void Then();
            void Else();

            void JumpToEnd();

        private:
            BytecodeLabels end_labels_;
            BytecodeLabels then_labels_;
            BytecodeLabels else_labels_;

            AstNode* node_;
            int block_coverage_then_slot_;
            int block_coverage_else_slot_;
            BlockCoverageBuilder* block_coverage_builder_;
        };

    } // namespace interpreter
} // namespace internal
} // namespace v8

#endif // V8_INTERPRETER_CONTROL_FLOW_BUILDERS_H_
